import { ApiMsgEnum, InputTypeEnum } from "./Enum";
import { IMsgClientSync, IMsgServerSync } from "../Interface/Msg";
import { IActorMove, IClientInput, IState, ITimePass } from "../Interface/State";
import { strdecode, strencode, toFixed } from "./Util";
const actorMoveDataLength = 14
const timePassDataLength = 13
const encodeBase = (index: number, name: ApiMsgEnum, data: any, da: DataView) => {
    da.setUint8(index++, name)
    da.setUint32(index, data.frameId || data.lastFrameId)
    index += 4
    return index
}
const encodeActorMove = (index: number, da: DataView, input: IClientInput) => {
    const { type, id, dt, direction } = input as IActorMove
    da.setUint8(index++, type)
    da.setUint8(index++, id)
    da.setFloat32(index, toFixed(dt))
    index += 4
    da.setFloat32(index, toFixed(direction.x))
    index += 4
    da.setFloat32(index, toFixed(direction.y))
    index += 4

    return index
}
const encodeTimePass = (index: number, da: DataView, input: IClientInput) => {
    const { type, seed, dt, totalTime } = input as ITimePass
    da.setUint8(index++, type)
    da.setUint32(index, seed)
    index += 4
    da.setFloat32(index, toFixed(dt))
    index += 4
    da.setFloat32(index, toFixed(totalTime))
    index += 4

    return index
}
export const binaryEncode = (name: ApiMsgEnum, data: any): DataView => {
    let index = 0
    if (name === ApiMsgEnum.MsgClientSync) {
        const { input } = data as IMsgClientSync
        if (input.type === InputTypeEnum.actorMove) {
            const ab = new ArrayBuffer(1 + 4 + actorMoveDataLength)
            const da = new DataView(ab)

            index = encodeBase(index, name, data, da)
            encodeActorMove(index, da, input as any)

            return da
        } else if (input.type === InputTypeEnum.timePass) {
            const ab = new ArrayBuffer(1 + 4 + timePassDataLength)
            const da = new DataView(ab)

            index = encodeBase(index, name, data, da)
            encodeTimePass(index, da, input as any)

            return da
        }
    } else if (name === ApiMsgEnum.MsgServerSync) {
        const { inputs, agentId, state } = data as IMsgServerSync

        let total = 6
        for (const input of inputs) {
            if (input.type === InputTypeEnum.actorMove) {
                total += actorMoveDataLength
            } else if (input.type === InputTypeEnum.timePass) {
                total += timePassDataLength
            }
        }

        const tempTotal = total
        if (agentId && state) {
            const str = JSON.stringify({ agentId, state })
            const ta = strencode(str)
            total += ta.length
        }

        const ab = new ArrayBuffer(total)
        const da = new DataView(ab)
        index = encodeBase(index, name, data, da)
        da.setUint8(index++, inputs.length)
        for (const input of inputs) {
            if (input.type === InputTypeEnum.actorMove) {
                index = encodeActorMove(index, da, input)
            } else if (input.type === InputTypeEnum.timePass) {
                index = encodeTimePass(index, da, input)
            }
        }

        if (agentId && state) {
            const str = JSON.stringify({ agentId, state })
            const ta = strencode(str)
            for (let i = 0; i < ta.length; i++) {
                da.setUint8(i + tempTotal, ta[i])
            }
        }

        return da
    }

    const str = JSON.stringify(data)
    const ta = strencode(str)
    const ab = new ArrayBuffer(ta.length + 1)
    const da = new DataView(ab)
    da.setUint8(0, name)
    for (let i = 0; i < ta.length; i++) {
        da.setUint8(i + 1, ta[i])
    }
    return da
}

const decodeActorMove = (index: number, da: DataView) => {
    const id = da.getUint8(index++)
    const dt = toFixed(da.getFloat32(index))
    index += 4
    const direction = { x: 0, y: 0 }
    direction.x = toFixed(da.getFloat32(index))
    index += 4
    direction.y = toFixed(da.getFloat32(index))
    index += 4

    return { type: InputTypeEnum.actorMove, id, dt, direction }
}
const decodeTimePass = (index: number, da: DataView) => {
    const seed = da.getUint32(index)
    index += 4
    const dt = toFixed(da.getFloat32(index))
    index += 4
    const totalTime = toFixed(da.getFloat32(index))
    index += 4

    return { type: InputTypeEnum.timePass, seed, dt, totalTime }
}
export const binaryDecode = (buffer: ArrayBuffer): any => {
    let index = 0
    const da = new DataView(buffer)
    const name = da.getUint8(index++)
    if (name === ApiMsgEnum.MsgClientSync) {
        const frameId = da.getUint32(index)
        index += 4
        const type = da.getUint8(index++)
        if (type === InputTypeEnum.actorMove) {
            return {
                name,
                data: {
                    frameId,
                    input: decodeActorMove(index, da)
                }
            }
        } else if (type === InputTypeEnum.timePass) {
            return {
                name,
                data: {
                    frameId,
                    input: decodeTimePass(index, da)
                }
            }
        }
    } else if (name === ApiMsgEnum.MsgServerSync) {
        const lastFrameId = da.getUint32(index)
        index += 4
        const len = da.getUint8(index++)
        const inputs: any = []
        for (let i = 0; i < len; i++) {
            const type = da.getUint8(index++)
            if (type === InputTypeEnum.actorMove) {
                const input = decodeActorMove(index, da)
                index += 13
                inputs.push(input)
            } else if (type === InputTypeEnum.timePass) {
                const input = decodeTimePass(index, da)
                index += 12
                inputs.push(input)
            }
        }

        let state: IState | null = null
        let agentId: number | null = null

        if (da.byteLength > index) {
            const ta = new Uint8Array(buffer.slice(index))
            const str = strdecode(ta)
            const data = JSON.parse(str)
            state = data.state
            agentId = data.agentId
        }

        return {
            name,
            data: {
                state,
                agentId,
                lastFrameId,
                inputs
            }
        }
    }

    const ta = new Uint8Array(buffer.slice(1))
    const str = strdecode(ta)
    const data = JSON.parse(str)
    return { name, data }

}